---
theme: gaia
_class: lead
paginate: true
backgroundColor: #fff
marp: true
math: mathjax
---

<style>
img[alt~="center"] {
  display: block;
  margin: 0 auto;
}
code {
    background: #000;
}
pre {
    background: #000;
}
</style>

# Программирование на основе классов и шаблонов

<style scoped>
h1 {
    font-size: 1.5rem;
}
h2 {
    font-size: 1rem;
}
</style>

![bg left:40% 80%](../images/iu5edu_logo.jpg)

## Принципы ОО дизайна

**Аладин** Дмитрий Владимирович

[iu5edu.ru/wiki/cpp2](https://iu5edu.ru/wiki/cpp2)

---

# План

<style scoped>
h1 {
    font-size: 1.2rem;
}
li {
    font-size: 1rem;
}
</style>

1. Критерии хорошего ОО (объектно-ориентированного) дизайна.
2. Принцип единственной ответственности.
3. Принцип открытости-закрытости.
4. Принцип подстановки Барбары Лисков.
5. Принцип разделения интерфейса.
6. Принцип инверсии зависимостей.
7. Принцип Деметера.
8. Принцип ацикличности зависимостей.

---

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.72rem;
}
</style>

> Вопрос **"Как писать хорошие программы на C++?"** очень похож на вопрос **"Как писать хорошую английскую прозу?"**. Есть два ответа: **"Знай, что ты хочешь сказать"** и **"Практикуйтесь. Подражайте хорошему письму"**. Оба варианта, по–видимому, так же подходят для C++, как и для английского, и так же трудны для понимания. Основным идеалом для программирования на C++ – как и для программирования на большинстве языков более высокого уровня - является **выражение концепций** (идей, понятий и т.д.) **из проекта непосредственно в коде**.
>
> Бьёрн Страуструп - Язык программирования C++

![bg right:40% 220%](./images/stroustrup.jpeg)

---

<style scoped>
p {
    font-size: 0.9rem;
}
</style>

![bg left:40% 200%](./images/questions-mem.jpg)

Какой дизайн нам нужен?

Какие концепции необходимо использовать?

Какую программу можно назвать хорошо спроектированной, а какую — плохо?

Если бы осьминоги умели считать, то какой бы системой они, скорее всего, пользовались?

Может ли человек умереть от компьютерного вируса?

---

# Поговорим про коня в вакууме

![bg right:40% 125%](./images/horse-in-vacuum.png)

<style scoped>
h1 {
    font-size: 1rem;
}
p {
    font-size: 0.75rem;
}
li {
    font-size: 0.75rem;
}
</style>

Возможно (*но это не точно*) хорошим, то есть наиболее приемлемым как для производителя, так и для потребителя программного продукта является тот дизайн, который обеспечит:

- **Наибольшую гибкость структуры ПО**;
- **Низкую стоимость сопровождения**;
- **Возможность повторного использования кода программ**.

*P.S.* **"Сферический конь в вакууме"** - совершенно невозможное для применения на практике, однако в теории "живет и работает" прекрасно.

---

![bg left:40% 130%](./images/programmer-superhuman.jpg)

> То, что в ООП не существует единственно правильных ответов, а только способы идентификации неправильных, лишний раз доказывает, что **программирование является искусством**.
>
> В. В. Мухортов, В. Ю. Рылов - Объектно-ориентированное программирование, анализ и дизайн

---

# Гибкость

![bg left:50% 90%](./images/flexibility.jpg)

— это возможность внесения изменений в код **без существенной его переработки**. Гибкость как свойство отображения требований пользователя на код программы есть мера устойчивости кода к изменениям требований.

---

![bg right:50% 90%](./images/customer-mem.jpg)

А новые требования пользователя точно будут адекватными?

*P.S.* [10 типов заказчиков.](https://fotostrana.ru/public/post/332287/1579617533/)

---

# Стоимость сопровождения

<style scoped>
h1 {
    font-size: 1.2rem;
}
</style>

— стоимость внесения изменений в фазе сопровождения ПО.

![height:450px center](./images/cost-mem.jpg)

---

# Повторное использование кода

<style scoped>
h1 {
    font-size: 1.2rem;
}
</style>

![bg left:50% 100%](./images/code-reuse-mem.gif)

— это возможность использовать единожды определенные абстракции (классы, модули) в разных компонентах одного и того же или различного ПО.

---

# Пути удовлетворения критериям хорошего ОО дизайна

![bg right:50% 100%](./images/samurai.jpg)

<style scoped>
h1 {
    font-size: 0.9rem;
}
p {
    font-size: 0.8rem;
}
li {
    font-size: 0.75rem;
}
</style>

- Разработка **дизайна иерархий классов** (как собственно классов, так и отношений между ними);
- Разработка **дизайна пакетов** (содержимого пакетов и связей между ними);
- Поиск и повторное использования **проверенных решений конкретных проблем**.

*P.S.* У самурая нет тестов, только прод!

---

# Разработка дизайна иерархий классов

![bg left:50% 120%](./images/Robert-Martin.png)

<style scoped>
h1 {
    font-size: 1.1rem;
}
p {
    font-size: 0.9rem;
}
</style>

— это про **объектную декомпозицию**!

[**Роберт Мартин**](https://www.youtube.com/@Cleancoders/videos) (*знаменитый в узких кругах блогер*) разработал пять принципов объектно-ориентированного программирования и проектирования, говоря о которых, с подачи **Майкла Фэзерса**, используют акроним **SOLID**.

---

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.9rem;
}
</style>

![bg right:50% 90%](./images/stole-code-mem.png)

Следующий материал успешно ~~украден~~ адаптирован с [хабра](https://habr.com/ru/companies/ruvds/articles/426413/), который является переводом с [blog.bitsrc.io](https://blog.bitsrc.io/solid-principles-every-developer-should-know-b3bfa96bb688).

Ну и еще использован методический материал из "В. В. Мухортов, В. Ю. Рылов - Объектно-ориентированное программирование, анализ и дизайн"

Адаптированный материал публикуется с сохранением условий распространения.

---

# Что такое SOLID?

![bg left:60% 90%](./images/Metal-Gear-Doge-mem.jpg)

---

# Расшифровка акронима SOLID

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.9rem;
}
</style>

![bg right:40% 110%](./images/solid-box.jpg)

**S**: Single Responsibility Principle (*Принцип единственной ответственности*).
**O**: Open-Closed Principle (*Принцип открытости-закрытости*).
**L**: Liskov Substitution Principle (*Принцип подстановки Барбары Лисков*).
**I**: Interface Segregation Principle (*Принцип разделения интерфейса*).
**D**: Dependency Inversion Principle (*Принцип инверсии зависимостей*).

---

# [Максимально кратко](https://habr.com/ru/articles/811305/) про каждый принцип

<style scoped>
h1 {
    font-size: 1.3rem;
}
</style>

**S** (*Принцип единственной ответственности*) — делай модули меньше.
**O** (*Принцип открытости-закрытости*) — делай модули расширяемыми.
**L** (*Принцип подстановки Барбары Лисков*) — наследуйся правильно.
**I** (*Принцип разделения интерфейса*) — дроби интерфейсы.
**D** (*Принцип инверсии зависимостей*) — используй интерфейсы.

---

# S: Принцип единственной ответственности

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.9rem;
}
</style>

**Каждый класс должен решать лишь одну задачу.**

![bg left:40% 90%](./images/responsibility.jpeg)

Класс должен быть ответственен лишь за что-то одно. Если класс отвечает за решение нескольких задач, его подсистемы, реализующие решение этих задач, **оказываются связанными друг с другом**. Изменения в одной такой подсистеме ведут к изменениям в другой.

---

<style scoped>
p {
    font-size: 0.9rem;
}
</style>

```cpp
class Animal {
 public:
  Animal(std::string name);
  std::string GetAnimalName();
  void SaveAnimal(Animal a);
};
```

Класс `Animal`, представленный здесь, описывает какое-то животное. Этот класс нарушает принцип единственной ответственности.

Он решает две, занимаясь работой с хранилищем данных в методе `SaveAnimal` и манипулируя свойствами объекта в конструкторе и в методе `GetAnimalName`.

**Как такая структура класса может привести к проблемам?**

---

**Если изменится порядок работы с хранилищем данных**, используемым приложением, то **придётся вносить изменения во все классы, работающие с хранилищем**.

![height:450px center](./images/domino-chain-reaction.jpg)

---

Исправленный вариант:

```cpp
class Animal {
 public:
  Animal(std::string name);
  std::string GetAnimalName();
};

class AnimalDB {
 public:
  Animal GetAnimal();
  void SaveAnimal(Animal a);
};
```

---

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.9rem;
}
</style>

![bg left:40% 90%](./images/stevefenton.png)

> Проектируя классы, мы должны стремиться к тому, чтобы **объединять родственные компоненты**, то есть такие, изменения в которых происходят по одним и тем же причинам. Нам следует стараться **разделять компоненты, изменения в которых вызывают различные причины**.
>
> [Стив Фентон](https://www.stevefenton.co.uk/) - софтверный панк, автор, программист-архитектор, прагматик / абстракционист и специалист широкого профиля.

---

# S: Классические примеры нарушения принципа

## Объекты, которые могут печатать сами себя

<style scoped>
h1 {
    font-size: 1.2rem;
}
h2 {
    font-size: 1rem;
}
p {
    font-size: 1rem;
}
</style>

```cpp
class Book {
 public:
  std::string GetTitle();
  std::string GetAuthor();
  void TurnPage();
  std::string PrintCurrentPage();
};
```

Нужно выделить интерфейс `Printer`, от которого можно будет сделать `PlainTextPrinter` и `HtmlPrinter`.

---

## Объекты, которые могут сохранять сами себя

<style scoped>
h1 {
    font-size: 1.2rem;
}
h2 {
    font-size: 1rem;
}
p {
    font-size: 1rem;
}
</style>

```cpp
class Book {
 public:
  std::string GetTitle();
  std::string GetAuthor();
  void TurnPage();
  void save(Book);
};
```

Нужно выделить интерфейс `Storage`, от которого можно будет сделать `DB` и `File`.

---

# S: Признаки нарушения принципа

- Затруднения с выбором подходящего имени класса;
- Большое количество методов в классе;
- Большое количество входящих и/или исходящих зависимостей;
- Систематическое нарушение других принципов дизайна.

---

# S по версии Р. Мартина

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 0.9rem;
}
</style>

**ORR** (One responsibility Rule) — правило единственности абстракции.

Класс должен обладать единственной ответственностью, реализуя ее полностью, реализуя ее хорошо и реализуя только ее.

![height:350px center](./images/Robert-Martin-S.png)

---

# O: Принцип открытости-закрытости

<style scoped>
h1 {
    font-size: 1.2rem;
}
h2 {
    font-size: 1rem;
}
p {
    font-size: 1rem;
}
</style>

Программные сущности (классы, модули, функции) должны быть открыты **для расширения**, но **не для модификации**.

![height:350px center](./images/extensions-mem.jpg)

---

```cpp
class Animal {
 public:
  Animal(std::string name);
  std::string GetAnimalName();
};

void AnimalSound(std::vector<Animal> a) {
  for (size_t i = 0; i < a.size(); i++) {
    if (a[i].GetAnimalName() == "lion") std::cout << "roar" << std::endl;
    if (a[i].GetAnimalName() == "mouse") std::cout << "squeak" << std::endl;
  }
}

int main() {
  std::vector<Animal> a{};
  a.push_back(Animal("lion"));
  a.push_back(Animal("mouse"));
  AnimalSound(a);
}
```

**Какие проблемы имеются тут?**

---

Функция `AnimalSound` не соответствует принципу открытости-закрытости. Изменения в содержимом контейнера:

```cpp
a.push_back(Animal("snake"));
```

влечет изменения `AnimalSound`:

```cpp
void AnimalSound(std::vector<Animal> a) {
  for (size_t i = 0; i < a.size(); i++) {
    if (a[i].GetAnimalName() == "lion") std::cout << "roar" << std::endl;
    if (a[i].GetAnimalName() == "mouse") std::cout << "squeak" << std::endl;
    if (a[i].GetAnimalName() == "snake") std::cout << "hiss" << std::endl;
  }
}
```

---

Исправленный вариант:

```cpp
class Animal {
 public:
  virtual std::string MakeSound() = 0;
};
class Lion : public Animal {
 public:
  virtual std::string MakeSound() override { return "roar"; };
};
class Squirrel : public Animal {
 public:
  virtual std::string MakeSound() override { return "squeak"; };
};
class Snake : public Animal {
 public:
  virtual std::string MakeSound() override { return "hiss"; };
};
```

---

<style scoped>
p {
    font-size: 0.8rem;
}
</style>

```cpp
void AnimalSound(std::vector<Animal*> a) {
  for (size_t i = 0; i < a.size(); i++) {
    std::cout << a[i]->MakeSound() << std::endl;
  }
}

int main() {
  std::vector<Animal*> a;
  a.push_back(new Lion());
  a.push_back(new Squirrel());
  a.push_back(new Snake());

  AnimalSound(a);
}
```

У класса `Animal` теперь есть виртуальный метод `MakeSound()`. При таком подходе нужно, чтобы классы, предназначенные для описания конкретных животных, расширяли бы класс Animal и реализовывали бы этот метод.

---

# O: Классический пример нарушения принципа

<style scoped>
h1 {
    font-size: 1.2rem;
}
h2 {
    font-size: 1rem;
}
p {
    font-size: 1rem;
}
</style>

```cpp
class Discount {
  double price{};

 public:
  double GiveDiscount() { return price * 0.2; }
};
```

Решено было разделить клиентов на две группы. Любимым (`fav`) клиентам даётся скидка в 20%, а VIP-клиентам (`vip`) — удвоенная скидка, то есть — 40%.

---

```cpp
class Discount {
  double price{};
  std::string customer;

 public:
  double GiveDiscount() {
    if (customer == "fav") {
      return price * 0.2;
    }
    if (customer == "vip") {
      return price * 0.4;
    }
    return 0;
  }
};
```

---

Исправленный вариант:

```cpp
class Discount {
  double price{};
 public:
  virtual double GiveDiscount() { return price * 0.2; }
};

class VIPDiscount : public Discount {
 public:
  virtual double GiveDiscount() override { 
    return Discount::GiveDiscount() * 2;
  }
};
```

Тут используется расширение возможностей классов, а не их модификация.

---

# L: Принцип подстановки Барбары Лисков

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 1rem;
}
</style>

![bg left:40% 90%](./images/replacement-mem.png)

**Необходимо, чтобы подклассы могли бы служить заменой для своих суперклассов.**

Если оказывается, что в коде проверяется тип класса, значит принцип подстановки нарушается.

---

<style scoped>
p {
    font-size: 0.9rem;
}
</style>

```cpp
int LionLegCount() { return 4; }
int SquirrelLegCount() { return 4; }
int SnakeLegCount() { return 0; }

void AnimalLegCount(std::vector<Animal*> a) {
  for (size_t i = 0; i < a.size(); i++) {
    if (dynamic_cast<Lion*>(a[i]) != nullptr)
      std::cout << LionLegCount() << std::endl;
    if (dynamic_cast<Squirrel*>(a[i]) != nullptr)
      std::cout << SquirrelLegCount() << std::endl;
    if (dynamic_cast<Snake*>(a[i]) != nullptr)
      std::cout << SnakeLegCount() << std::endl;
  }
}
```

**Что тут нарушено?**

*P.S.* [Статья](https://www.linkedin.com/pulse/how-get-real-type-object-c-konstantin-nikitin/?trk=portfolio_article-card_title) о том, как получить реальный тип объекта в C++.

---

Функция `AnimalLegCount` нарушает и принцип подстановки, и принцип открытости-закрытости.

Для того чтобы эта функция не нарушала принцип подстановки, нужно выполнить требование, сформулированное Стивом Фентоном:

> Методы, принимающие или возвращающие значения с типом некоего суперкласса должны также принимать и возвращать значения, типами которых являются его подклассы.

---

А мы такое уже делали:

```cpp
class Animal {
 public:
  virtual std::string MakeSound() = 0;
  virtual int LegCount() = 0;
};

class Lion : public Animal {
 public:
  virtual std::string MakeSound() override { return "roar"; };
  virtual int LegCount() override { return 4; }
};

void AnimalSound(std::vector<Animal*> a) {
  for (size_t i = 0; i < a.size(); i++) {
    std::cout << a[i]->MakeSound() << std::endl;
  }
}

void AnimalLegCount(std::vector<Animal*> a) {
  for (size_t i = 0; i < a.size(); i++) {
    std::cout << a[i]->LegCount() << std::endl;
  }
}
```

---

# L по версии Р. Мартина

<style scoped>
h1 {
    font-size: 1rem;
}
p {
    font-size: 0.8rem;
}
</style>

**LSP** (Liskov Substitution Principle) — принцип подстановки Лисковой.

Методы, принимающие в качестве параметра указатели и ссылки на объекты базового класса должны иметь возможность **использовать эти объекты без необходимости знать, к какому классу** (базовому или любому из производных) **они принадлежат**.

![height:320px center](./images/Robert-Martin-L.png)

---

# I: Принцип разделения интерфейса

<style scoped>
h1 {
    font-size: 1rem;
}
p {
    font-size: 0.8rem;
}
</style>

Создавайте узкоспециализированные интерфейсы, предназначенные для конкретного клиента. **Клиенты не должны зависеть от интерфейсов, которые они не используют.**

Этот принцип направлен на устранение недостатков, связанных с реализацией больших интерфейсов.

![height:320px center](./images/Separation-mem.png)

---

Рассмотрим интерфейс Shape:

```cpp
class Shape {
 public:
  virtual void DrawCircle() = 0;
  virtual void DrawSquare() = 0;
  virtual void DrawRectangle() = 0;
};
```

Он описывает методы для рисования кругов (`DrawCircle`), квадратов (`DrawSquare`) и прямоугольников (`DrawRectangle`). В результате классы, реализующие этот интерфейс и представляющие отдельные геометрические фигуры, такие, как круг (`Circle`), квадрат (`Square`) и прямоугольник (`Rectangle`), должны содержать реализацию всех этих методов.

---

```cpp
class Circle : public Shape {
 public:
  void DrawCircle() override;
  void DrawSquare() override;
  void DrawRectangle() override;
};

class Square : public Shape {
 public:
  void DrawCircle() override;
  void DrawSquare() override;
  void DrawRectangle() override;
};

class Rectangle : public Shape {
 public:
  void DrawCircle() override;
  void DrawSquare() override;
  void DrawRectangle() override;
};
```

**Странный получился код? А чем еще это грозит?**

---

Предположим, мы решим добавить в интерфейс `Shape` ещё один метод, `DrawTriangle`, предназначенный для рисования треугольников:

```cpp
class Shape {
 public:
  virtual void DrawCircle() = 0;
  virtual void DrawSquare() = 0;
  virtual void DrawRectangle() = 0;
  virtual void DrawTriangle() = 0;
};
```

Это приведёт к тому, что классам, представляющим конкретные геометрические фигуры, придётся реализовывать ещё и метод `DrawTriangle`. В противном случае возникнет ошибка.

---

Исправленный вариант:

```cpp
class Shape {
 public:
  virtual void Draw() = 0;
};

class ICircle: public Shape {
 public:
  virtual void DrawCircle() = 0;
};

...

class IRectangle {
 public:
  virtual void DrawRectangle() = 0;
};


class Rectangle : public IRectangle {
 public:
  void DrawCircle() override;
  void Draw() override;
};
```

---

# I по версии Р. Мартина

![bg left:40% 90%](./images/Robert-Martin-I.png)

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 1rem;
}
</style>

**ISP** (Interface Segregation Principle) — принцип разделения интерфейсов

**Классы не должны зависеть от контрактов, которых они не используют.**

*P.S.* На фото Роберт Мартин рассказывает о SOLID джуну

---

# D: Принцип инверсии зависимостей

<style scoped>
h1 {
    font-size: 1rem;
}
p {
    font-size: 0.8rem;
}
</style>

**Объектом зависимости должна быть абстракция, а не что-то конкретное.**

Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

![height:350px center](./images/dependency-inversions.jpg)

---

```cpp
class XMLHttpService {
    void GetRequest();
    void PostRequest();
};

class Http {
 public:
  Http(XMLHttpService service);
  void Get(/*args*/);
  void Post(/*args*/);
};
```

Здесь класс `Http` представляет собой высокоуровневый компонент, а `XMLHttpService` — низкоуровневый.

**Где в структуре скрываются проблемы?**

---

1. **Нарушение принципа инверсии зависимостей**, а именно "модули верхних уровней не должны зависеть от модулей нижних уровней".
2. Класс `Http` вынужденно зависит от класса `XMLHttpService`. Если мы решим изменить механизм, используемый классом `Http` для взаимодействия с сетью — скажем, это будет Node.js-сервис или, например, сервис-заглушка, применяемый для целей тестирования (англ. *mock* - имитация), нам придётся отредактировать все экземпляры класса `Http`, изменив соответствующий код. Это **нарушает принцип открытости-закрытости**.

---

Исправленный вариант:

```cpp
class Connection {
  virtual void GetRequest() = 0;
  virtual void PostRequest() = 0;
};

class XMLHttpService : public Connection {
  virtual void GetRequest() override;
  virtual void PostRequest() override;
};

class MockService : public Connection {
  virtual void GetRequest() override;
  virtual void PostRequest() override;
};

class Http {
 public:
  Http(Connection* service);
  void Get(/*args*/);
  void Post(/*args*/);
};
```

---

Как можно заметить, здесь высокоуровневые и низкоуровневые модули зависят от абстракций. Класс `Http` (высокоуровневый модуль) зависит от интерфейса `Connection` (абстракция). Классы `XMLHttpService` и `MockService` (низкоуровневые модули) также зависят от интерфейса `Connection`.

Кроме того, стоит отметить, что **следуя принципу инверсии зависимостей**, мы соблюдаем и **принцип подстановки Барбары Лисков**. А именно, оказывается, что типы `XMLHttpService` и `MockService` могут служить заменой базовому типу `Connection`.

---

# D по версии Р. Мартина

![bg right:45% 125%](./images/Robert-Martin-D.png)

<style scoped>
h1 {
    font-size: 1.1rem;
}
p {
    font-size: 0.9rem;
}
</style>

**DIP** (Dependency Inversion Principle) — принцип инверсии зависимости

Модули высокого уровня не должны зависеть от модулей низкого. И те, и другие, должны зависеть от абстракций. Абстракции не должны зависеть от деталей реализации.

*P.S.* Роберт Мартин показывает максимальную оценку за экзамен прогульщиков.

---

# Принцип Деметера

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 1rem;
}
</style>

> Каждый элемент программы должен обладать **ограниченным знанием о других элементах** и **использовать только тесно связанные с ним элементы**.
>
> Ян Холланд

Оригинальная формулировка этого принципа, возникшая в процессе работы над одноименным проектом (`Demeter`) в Северо-Восточный университет (Бостон) осенью 1987 года.

Принцип Деметера **не ограничивается классами**.

---

![bg left:45% 90%](./images/Demeter.png)

Одна из формулировок принципа Деметера звучит так:

> Разговаривайте только с близкими друзьями. Никогда не разговаривайте с незнакомцами.

---

<style scoped>
p {
    font-size: 0.9rem;
}
pre {
    font-size: 0.9rem;
}
</style>

> Минимизируйте связывание между модулями
>
> Э. Хант, Д. Томас. "Программист-прагматик"

[Пример](https://ru.stackoverflow.com/questions/524401/Чистый-код-Непонятный-закон-Деметры) из книги:

```cpp
class A {
 public:
  void setActive();
};
class B {
 public:
  void invert();
};
class C {
 public:
  void print();
};
```

---

```cpp
void Demeter::example(B& b) {
  int f = func();  // 1
  b.invert();  // 2
  a = new A();
  a->setActive();  // 3
  C c;
  c.print();  // 4
}
```

Закон Деметры для функций гласит, что любой метод некоторого объекта может обращаться только к методам принадлежащим: самим себе (`1`); любым параметрам , переданным в метод (`2`); любым создаваемым им объектам (`3`); любым непосредственно содержащимся объектам компонентов (`4`).

---

<style scoped>
p {
    font-size: 0.9rem;
}
</style>

![bg right:45% 100%](./images/Grady-Booch.jpg)

> Основной эффект от применения закона Деметера — **создание слабо связанной иерархии классов, детали реализации которых скрыты друг от друга**. Такие классы очень незагроможденны, чтобы понять один класс, вы не должны понимать детали многих других классов.
>
> Гради Буч, один из создателей UML

---

# Принцип ацикличности зависимостей (Р. Мартин)

<style scoped>
h1 {
    font-size: 1.2rem;
}
p {
    font-size: 1rem;
}
</style>

**ADP** (Acyclic Dependencies Principle) — принцип ацикличности зависимостей

Структура зависимостей между элементами (классами, пакетами, методами) должна представлять собой **направленный ациклический граф**.

Принцип в равной степени применим **ко всем элементам ПО**.

---

Ориентированный ациклический граф:

![height:350px center](./images/acyclic-graph.png)

[Изменение компонента](http://agilemindset.ru/зависимости-между-компонентами/) `Database` (который нужно собрать вместе с компонентов `Entitities`) затронет только компонент `Main`.

---

<style scoped>
p {
    font-size: 0.9rem;
}
</style>

Ориентированный граф с циклами:

![height:350px center](./images/cyclic-graph.png)

Изменение `Database` требует проверки работы с `Entities`, но `Entities` зависит от `Authorizer`, который зависит от `Interactors`. По сути `Database`, `Entities`, `Authorizer`,`Interactors` вместе становятся одним большим компонентом.

---

# Стратегии прерывания цикла

<style scoped>
h1 {
    font-size: 1.2rem;
}
li {
    font-size: 1rem;
}
p {
    font-size: 1rem;
}
</style>

![bg right:30% 120%](./images/scissors.jpg)

Циклы в зависимостях делают код более хрупким. Стратегии разорвать цепочку циклических зависимостей:

- Принцип инверсии зависимостей;
- Создать новый пакет и переместить туда общие зависимости.

---

# Вопросы?

<style scoped>
p {
  text-align: center;
}
</style>

![height:430px center](./images/Robert-Martin-Questions.png)

*P.S.* Р. Мартин не доволен тем, что джуны не читают его книг
